// Copyright (C) 2007 Jesse Jones
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using Mono.Cecil;
using Mono.Cecil.Cil;
using System;
using System.Collections.Generic;
using System.Text;
using Smokey.Framework;
using Smokey.Framework.Instructions;
using Smokey.Framework.Support;

namespace Smokey.Internal.Rules
{	
	internal sealed class FinalizeableRule : Rule
	{				
		public FinalizeableRule(AssemblyCache cache, IReportViolations reporter) 
			: base(cache, reporter, "D1066")
		{
		}
				
		public override void Register(RuleDispatcher dispatcher) 
		{
			dispatcher.Register(this, "VisitBegin");
			dispatcher.Register(this, "VisitThrow");
			dispatcher.Register(this, "VisitEnd");
		}
				
		public void VisitBegin(BeginMethod begin)
		{			
			m_isEmpty = false;
			m_notDisposable = false;
			m_checkThrows = false;
			m_doesThrow = false;
			
			MethodDefinition method = begin.Info.Method;
			if (method.Matches("System.Void", "Finalize") && !begin.Info.Type.IsCompilerGenerated())
			{
				Log.DebugLine(this, "-----------------------------------"); 
				Log.DebugLine(this, "checking {0:F}", begin.Info.Instructions);				

				if (begin.Info.Instructions.Length == 0 || begin.Info.Instructions[0].Untyped.OpCode.Code == Code.Leave)
					m_isEmpty = true;
					
				if (!begin.Info.Type.TypeOrBaseImplements("System.IDisposable", Cache))
					m_notDisposable = true;
			
				m_checkThrows = begin.Info.Instructions.TryCatchCollection.Length <= 1;		// normally a try/finally block generated by the compiler
			}
		}

		public void VisitThrow(Throw t)
		{
			Unused.Value = t;

			if (m_checkThrows)
			{
				m_doesThrow = true;
			}
		}

		public void VisitEnd(EndMethod end)
		{			
			string details = string.Empty;
			
			if (m_isEmpty)
				details += "Finalizer is empty." + Environment.NewLine;
			
			if (m_doesThrow)
				details += "Finalizer throws." + Environment.NewLine;
				
			if (m_notDisposable)
				details += "The type does not implement IDisposable." + Environment.NewLine;
				
			details = details.Trim();
			if (details.Length > 0)
			{
				Log.DebugLine(this, "Details: {0}", details);
				Reporter.MethodFailed(end.Info.Method, CheckID, 0, details);
			}
		}
		
		private bool m_isEmpty;
		private bool m_notDisposable;
		
		private bool m_checkThrows;
		private bool m_doesThrow;
	}
}

